﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace BrasilFramework.Controle.Silverlight
{
    public class PainelCarrossel : Panel
    {
    
        public PainelCarrossel()
        {
            Movimentou = true;
        }

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            Desenhar();
        }

        #region Propriedades

        public static readonly DependencyProperty AlturaElipsePropriedade = DependencyProperty.Register("AlturaElipse",
                                                                                             typeof(double),
                                                                                             typeof(PainelCarrossel),
                                                                                             new PropertyMetadata(10.00));

        public static readonly DependencyProperty LarguraElipsePropriedade = DependencyProperty.Register("LarguraElipse",
                                                                                              typeof(double),
                                                                                              typeof(PainelCarrossel),
                                                                                              new PropertyMetadata(200.00));

        public static readonly DependencyProperty DirecaoPropriedade = DependencyProperty.Register("Direcao", typeof(Direcao),
                                                                                        typeof(PainelCarrossel),
                                                                                        new PropertyMetadata(
                                                                                            Direcao.Esquerda));

        public static readonly DependencyProperty ExecutarAutomaticamentePropriedade =
            DependencyProperty.Register("ExecutarAutomaticamente", typeof(bool), typeof(PainelCarrossel),
                                        new PropertyMetadata(true));

        public static readonly DependencyProperty ElementoSelecionadoPropriedade =
            DependencyProperty.Register("ElementoSelecionado", typeof(FrameworkElement), typeof(PainelCarrossel),
                                        new PropertyMetadata(null));


        public double LarguraElipse
        {
            get { return (double)GetValue(LarguraElipsePropriedade); }
            set { SetValue(LarguraElipsePropriedade, value); }
        }

        public double AlturaElipse
        {
            get { return (double)GetValue(AlturaElipsePropriedade); }
            set { SetValue(AlturaElipsePropriedade, value); }
        }

        public Direcao Direcao
        {
            get { return (Direcao)GetValue(DirecaoPropriedade); }
            set { SetValue(DirecaoPropriedade, value); }
        }

        public FrameworkElement ElementoSelecionado
        {
            get { return (FrameworkElement)GetValue(ElementoSelecionadoPropriedade); }
            set { SetValue(ElementoSelecionadoPropriedade, value); }
        }

        #endregion

        #region Constantes

        private const double ALFA_MAXIMO = 1;
        private const double ESCALA_MAXIMA = 1;
        private const double ALFA_MINIMO = 0.8;
        private const double ESCALA_MINIMA = 0.4;
        private const double VELOCIDADE = 0.1;
        private const double POSICAO_VISUALIZACAO = 0;

        #endregion

        #region Variáveis Internas

        private Dictionary<FrameworkElement, ElementoCaracteristica> listaFilhoInterno =
            new Dictionary<FrameworkElement, ElementoCaracteristica>();

        private int proximaAlturaProfundidade;
        private double distancia;

        private bool movimentou;

        private bool Movimentou
        {
            get { return movimentou; }
            set
            {
                if (value && !movimentou)
                    CompositionTarget.Rendering += CompositionTarget_Rendering;

                movimentou = value;
            }
        }
        private List<double> listaAlfa;
        private List<double> listaEscala;

        #endregion

        #region Eventos

        public delegate void PainelCarrosselSelectedItemHandler(
            PainelCarrossel sender, PainelCarrosselSelectedItemEventArgs e);

        public event PainelCarrosselSelectedItemHandler SelectedItem;

        private void OnItemMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var elemento = sender as FrameworkElement;

            if (elemento == null) return;
            ElementoCaracteristica elementoCaracteristica = listaFilhoInterno[elemento];

            if (elementoCaracteristica.indice == 1)
            {
                if (!Movimentou && SelectedItem != null)
                    SelectedItem(this, new PainelCarrosselSelectedItemEventArgs((FrameworkElement)sender));
            }
            else if (elementoCaracteristica.transposicaoDestino.X > Centro.X)
            {
                int proximaPosicao = listaFilhoInterno.Count - elementoCaracteristica.indice + 1;
                Mover(proximaPosicao, Direcao.Direita);
            }
            else
            {
                int proximaPosicao = elementoCaracteristica.indice - 1;
                Mover(proximaPosicao, Direcao.Esquerda);
            }
        }

        #endregion

        private int quantidadeAtualizacao;

        private void Desenhar()
        {
            foreach (FrameworkElement element in listaFilhoInterno.Keys)
            {
                ElementoCaracteristica elementoCaracteristica = listaFilhoInterno[element];
                elementoCaracteristica.transposicaoOrigem.X = -element.ActualWidth / 2.0;
                elementoCaracteristica.transposicaoOrigem.Y = -element.ActualHeight / 2.0;
                elementoCaracteristica.transposicaoDestino.X = LarguraElipse * Math.Cos(elementoCaracteristica.tempo) + Centro.X;
                elementoCaracteristica.transposicaoDestino.Y = AlturaElipse * Math.Sin(elementoCaracteristica.tempo) + Centro.Y;
                elementoCaracteristica.tempo = elementoCaracteristica.tempo + VELOCIDADE * (elementoCaracteristica.destino - elementoCaracteristica.tempo);
                Canvas.SetZIndex(element, Convert.ToInt32(elementoCaracteristica.transposicaoDestino.Y));
                distancia = 6.283185E+000 / listaFilhoInterno.Count;

                if (quantidadeAtualizacao <= 1) continue;

                elementoCaracteristica.escalaDestino.ScaleX = elementoCaracteristica.escalaDestino.ScaleX +
                                                                VELOCIDADE * (elementoCaracteristica.escala - elementoCaracteristica.escalaDestino.ScaleX);
                elementoCaracteristica.escalaDestino.ScaleY = elementoCaracteristica.escalaDestino.ScaleX;
                element.Opacity = element.Opacity + VELOCIDADE * (elementoCaracteristica.alfa - element.Opacity);
            }

            if (Movimentou) Movimentou = false;

            if (++quantidadeAtualizacao <= 250) return;

            CompositionTarget.Rendering -= CompositionTarget_Rendering;
            quantidadeAtualizacao = 0;
        }

        private Point Centro
        {
            get { return new Point(ActualWidth / 2, ActualHeight / 2); }
        }

        public void AdicionarFilho(FrameworkElement item)
        {
            TranslateTransform transposicaoDestino = new TranslateTransform();
            RotateTransform rotacaoDestino = new RotateTransform();
            ScaleTransform escalaDestino = new ScaleTransform();
            SkewTransform torcerDestino = new SkewTransform();
            TranslateTransform transposicaoOrigem = new TranslateTransform();
            item.RenderTransform = new TransformGroup
                                        {
                                            Children =
                                                new TransformCollection
                                                    {
                                                        rotacaoDestino,
                                                        escalaDestino,
                                                        torcerDestino,
                                                        transposicaoDestino,
                                                        transposicaoOrigem
                                                    }
                                        };
            ElementoCaracteristica elementoCaracteristica = new ElementoCaracteristica
                             {
                                 rotacaoDestino = rotacaoDestino,
                                 escalaDestino = escalaDestino,
                                 torcerDestino = torcerDestino,
                                 transposicaoDestino = transposicaoDestino,
                                 transposicaoOrigem = transposicaoOrigem
                             };

            listaFilhoInterno.Add(item, elementoCaracteristica);
            item.MouseLeftButtonUp += OnItemMouseLeftButtonUp;
            proximaAlturaProfundidade++;
            Children.Add(item);
            CarregarFilhos();
        }

        private void CarregarFilhos()
        {
            int i = 1;
            InicializarEscalaTransparencia();
            foreach (FrameworkElement element in listaFilhoInterno.Keys)
            {
                ElementoCaracteristica elementoCaracteristica = listaFilhoInterno[element];
                elementoCaracteristica.destino = 6.283185E+000 * (i - 1) / listaFilhoInterno.Count + (1.570796E+000 + POSICAO_VISUALIZACAO);
                elementoCaracteristica.origem = elementoCaracteristica.destino;
                elementoCaracteristica.indice = i;
                elementoCaracteristica.escala = listaEscala[i - 1];
                elementoCaracteristica.alfa = listaAlfa[i - 1];
                i++;
                if (elementoCaracteristica.indice == 1)
                {
                    ElementoSelecionado = element;
                }
            }
        }

        private void InicializarEscalaTransparencia()
        {
            listaEscala = new List<double>();
            listaAlfa = new List<double>();
            double escalaMedida = (ESCALA_MAXIMA - ESCALA_MINIMA) / (listaFilhoInterno.Count / 2.0);
            double alfaMedida = (ALFA_MAXIMO - ALFA_MINIMO) / (listaFilhoInterno.Count / 2.0);
            int indiceCentro = ((listaFilhoInterno.Count / 2.0) > Convert.ToInt32(listaFilhoInterno.Count / 2))
                           ? Convert.ToInt32(listaFilhoInterno.Count / 2) + 1
                           : Convert.ToInt32(listaFilhoInterno.Count / 2);

            for (int i = 0; i < listaFilhoInterno.Count; i++)
            {
                if (i < indiceCentro)
                {
                    listaEscala.Add((ESCALA_MAXIMA - escalaMedida * i));
                    listaAlfa.Add((ALFA_MAXIMO - alfaMedida * i));
                }
                else
                {
                    listaEscala.Add((ESCALA_MINIMA + escalaMedida * (i - indiceCentro)));
                    listaAlfa.Add((ALFA_MINIMO + alfaMedida * (i - indiceCentro)));
                }
            }
        }

        public void RemoverFilhos()
        {
            foreach (FrameworkElement element in listaFilhoInterno.Keys)
            {
                Children.Remove(element);
            }
            listaFilhoInterno = new Dictionary<FrameworkElement, ElementoCaracteristica>();
        }

        public void Mover(int posicaoItem, Direcao direcao)
        {
            Movimentou = true;

            foreach (FrameworkElement element in listaFilhoInterno.Keys)
            {
                ElementoCaracteristica elementoCaracteristica = listaFilhoInterno[element];
                if (direcao == Direcao.Direita)
                {
                    elementoCaracteristica.destino = elementoCaracteristica.origem + 6.283185E+000 / listaFilhoInterno.Count + distancia * (posicaoItem - 1);
                    elementoCaracteristica.origem = elementoCaracteristica.destino;
                    for (int j = 1; j <= posicaoItem; ++j)
                    {
                        if (elementoCaracteristica.indice == listaFilhoInterno.Count)
                        {
                            elementoCaracteristica.indice = 1;
                            continue;
                        }
                        ++elementoCaracteristica.indice;
                    }
                }
                else
                {
                    elementoCaracteristica.destino = elementoCaracteristica.origem - 6.283185E+000 / listaFilhoInterno.Count - distancia * (posicaoItem - 1);
                    elementoCaracteristica.origem = elementoCaracteristica.destino;
                    for (int j = 1; j <= posicaoItem; ++j)
                    {
                        if (elementoCaracteristica.indice == 1)
                        {
                            elementoCaracteristica.indice = listaFilhoInterno.Count;
                            continue;
                        }
                        --elementoCaracteristica.indice;
                    }
                }
                if (elementoCaracteristica.indice == 1)
                {
                    ElementoSelecionado = element;
                }

                elementoCaracteristica.escala = listaEscala[elementoCaracteristica.indice - 1];
                elementoCaracteristica.alfa = listaAlfa[elementoCaracteristica.indice - 1];

            }

            Desenhar();
        }

        #region Disposição e Tamanho

        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach (UIElement elemento in Children)
            {
                Point location = new Point();
                elemento.Arrange(new Rect(location, elemento.DesiredSize));
            }
            return finalSize;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement elemento in Children)
            {
                elemento.Measure(availableSize);
            }
            Clip = new RectangleGeometry { Rect = new Rect(new Point(), availableSize) };
            return availableSize;
        }

        #endregion

        #region Classes Internas - Auxiliares

        public class PainelCarrosselSelectedItemEventArgs : RoutedEventArgs
        {
            public readonly FrameworkElement SelectedItem;

            public PainelCarrosselSelectedItemEventArgs(FrameworkElement item)
            {
                SelectedItem = item;
            }
        }

        private class ElementoCaracteristica
        {
            internal double alfa;
            internal int indice;
            internal TranslateTransform transposicaoOrigem;
            internal double origem;
            internal double escala;
            internal double tempo;
            internal double destino;
            internal RotateTransform rotacaoDestino;
            internal ScaleTransform escalaDestino;
            internal SkewTransform torcerDestino;
            internal TranslateTransform transposicaoDestino;
        }

        #endregion
    }

    public enum Direcao
    {
        Esquerda,
        Direita
    }
}